64장. 실전 1 — 요구사항 하나로 기능 개발하기
지금까지 만든 것을 전부 써서
기능 하나를 처음부터 끝까지 한다.
과제는 20장에서 봤던 그 티켓이다.
[PAY-2841] 결제 실패 시 재시도 지원
고객사에서 PG 일시 오류로 결제가 실패하는 건이
하루 30건 정도 발생. 자동 재시도가 필요합니다.
세 줄이다.
준비 상태
CLAUDE.md 있음 (계층·컨벤션·금지사항)
settings.json 있음
Skills add-api, migration-review, security-review, handoff
Subagents explorer, planner, reviewer, db-reviewer
빠른 테스트 ./gradlew test --tests '*UnitTest' (12초)
아키텍처 테스트 payment 도메인 포함
Jira · Grafana MCP 읽기 전용
61장의 HARNESS.md 에 정리된 상태다.
세션 1 — 티켓을 Task로 (25분)
PAY-2841 티켓과 댓글을 읽고 작업 Task로 변환해줘.
- 코드를 확인해서 영향받을 파일을 Scope에 넣어줘
- 내가 결정해야 하는 항목은 "결정 필요" 로 모아줘
- 구현은 하지 마
55장에서 말한 대로 댓글에 결정이 있었다.
댓글 (기획자, 3일 전):
"카드 한도 초과는 재시도하면 안 됩니다. 고객이 놀랍니다"
댓글 (PG 담당, 1일 전):
"멱등키는 이미 보내고 있는데 재시도 시 그대로 쓰면 됩니다"
두 번째가 중요하다.
30장에서 말한 재시도의 전제가 이미 있었다.
Agent가 뽑아온 결정 필요 는 넷이었다.
1. 재시도 이력을 별도 테이블로? → 아니오. payment 컬럼 추가
2. 3회 실패 후 처리? → 기존 슬랙 알림 채널 재사용
3. 재시도 중 사용자에게 노출? → 노출 안 함
4. 배치 정산이 같은 PgClient 사용 → 영향 확인 필요
🔥 4번은 Agent가 코드를 보고 찾아낸 것이다.
내가 몰랐던 항목이다.
사람이 결정한 것 ① — 네 항목의 답.
세션 2 — 조사와 계획 (40분)
explorer 로 PgClient 를 사용하는 모든 경로를 조사해줘.
결과는 docs/pg-client-usage.md 에 저장하고 요약만 보고해줘.
50장의 Context 격리를 쓴다.
파일 30여 개를 읽었지만 내 세션에는 요약만 들어왔다.
PgClient 사용처 4곳
PaymentService.pay() 결제
PaymentCancelHandler.cancel() 취소
SettlementJob.reconcile() 정산 배치 ⚠️
AdminPaymentService.forcePay() 관리자 강제 결제 ⚠️
⚠️ 뒤의 두 개가 문제였다.
정산 배치에 재시도가 붙으면
배치 시간이 3배로 늘어날 수 있다.
planner 로 계획을 세워줘. @docs/pg-client-usage.md 참고.
재시도는 결제 경로에만 적용하고 싶어.
계획서가 왔다. 21장의 다섯 요소 + 대안.
## 6. 대안
A. PgClient 에 @Retryable — 모든 호출자에 적용됨 ❌
B. 결제 전용 래퍼 클래스 추가 — 관심사 분리 ✅
C. 호출부에서 재시도 — 중복 코드
사람이 결정한 것 ② — B안 선택.
계획을 tasks/PAY-2841.md 에 저장하고 세션을 닫았다.
세션 3 — 분해 (10분)
22장의 방식으로 쪼갰다.
1. 재시도 대상 예외 분류 (PgTimeout / PgServerError)
2. 멱등키 재사용 검증 강화
3. retryCount 컬럼 마이그레이션
4. PaymentPgClient 래퍼 + 재시도 적용
5. 3회 실패 시 알림
6. 통합 테스트
의존 관계상 1·2·3은 독립,
4는 셋 다 필요, 5·6은 4 이후.
53장의 기준으로 병렬을 검토했지만
1·2가 같은 파일을 건드려서 순차로 갔다.
세션 4~6 — Task 1·2·3 구현 (각 30~50분)
각 세션이 커밋 하나로 끝났다.
Task 3에서 사고가 났다.
-- Agent가 만든 마이그레이션
ALTER TABLE payments ADD COLUMN retry_count INT NOT NULL DEFAULT 0;
migration-review Skill을 돌렸다.
/migration-review
## 스키마 변경
- NOT NULL 을 백필 전에 걸지 않았는가
⚠️ 문제있음. payments 테이블은 약 1,200만 행입니다.
DEFAULT 가 있어 논리적으로는 통과하지만, MySQL 버전에 따라
전체 테이블 rewrite 가 발생해 장시간 잠금 위험이 있습니다.
🔥 48장에서 만든 체크리스트가 실제로 잡았다.
27장에서 문장으로만 알던 규칙이
Skill 안에서 자동으로 적용됐다.
nullable 로 만들고, 백필은 별도 마이그레이션으로 분리해줘.
사람이 결정한 것 ③ — 3단계로 나눌지, DB 버전을 확인하고 갈지.
확인 결과 안전한 버전이었지만 나누는 쪽을 택했다.
세션 7 — Task 4 구현 (1시간)
핵심 작업이다.
지시에 30장의 항목을 넣었다.
@tasks/PAY-2841.md 의 Task 4 를 진행하자.
이번 범위:
- PaymentPgClient 래퍼 생성, 재시도 적용
- Task 1의 예외 분류 사용
이번 범위 아님:
- 알림 (Task 5)
- 정산 배치·관리자 경로 (재시도 적용 안 함)
완료 조건:
- 타임아웃 시 3회 재시도 테스트
- 잔액 부족은 재시도 안 함 테스트
- 같은 멱등키로 재시도해도 결제 1건 테스트
- 기존 결제 테스트 24건 통과
세 번째 완료 조건이 이 작업의 핵심이었다.
MockWebServer로 실패를 주입하는 테스트가 만들어졌고,
전부 통과했다.
⚠️ 여기서 놓친 것이 하나 있었다.
65장에서 드러난다.
세션 8 — 독립 Review (30분)
/clear 후 새 세션에서 시작했다.
52장의 원칙대로
구현 대화를 주지 않았다.
reviewer 와 db-reviewer 로 검토해줘.
대상: git diff main...HEAD
계획서: @tasks/PAY-2841.md
지적 네 개가 왔다.
| # | 지적 | 심각도 | 판단 |
|---|---|---|---|
| 1 | 재시도 중 커넥션 점유 시간 미검증 | 높음 | 반영 |
| 2 | 알림 발송이 트랜잭션 안 (Task 5 예정) | 높음 | 반영 |
| 3 | 래퍼 클래스에 로깅 없음 | 낮음 | 부채로 |
| 4 | PgClient 를 deprecated 표시 권장 | 낮음 | 부채로 |
사람이 결정한 것 ④ — 1·2만 반영, 3·4는 tasks/tech-debt.md 로.
52장에서 말한 대로,
전부 반영하면 범위가 커진다.
PR과 배포 (다음 날)
이 브랜치의 커밋 6개를 읽고 PR 설명 초안을 만들어줘.
- 무엇을 왜 바꿨는지
- 리뷰어가 중점적으로 볼 부분
- 테스트한 내용
사람이 결정한 것 ⑤ — PR 생성, 리뷰어 지정.
사람이 결정한 것 ⑥ — 배포 승인.
59장의 세 관문이 다 작동했다.
정리
총 소요 약 6시간 (2일에 걸쳐)
커밋 6개, 각 40~120줄
세션 8개
사람이 결정한 지점 6곳
Agent가 찾아낸 것 내가 몰랐던 호출 경로 2곳, 마이그레이션 위험
⚠️ 6시간은 짧지 않다.
직접 했으면 하루 반쯤 걸렸을 작업이고,
아마 정산 배치 영향을 놓쳤을 것이다.
🔥 그리고 6시간 중 절반은 검토와 결정이었다.
1장에서 말한 시간 배분의 이동이
실제로 이렇게 나타난다.
이 장의 핵심
- 티켓 본문이 아니라 댓글에 결정이 흩어져 있었다
- Agent가 내가 몰랐던 호출 경로 두 곳을 찾아냈다
- 조사를 Subagent에 위임해 메인 Context를 깨끗하게 유지했다
- 계획서의 대안 항목이 더 나은 설계로 이어졌다
migration-reviewSkill이 1,200만 행 테이블의 위험을 잡았다- 문장으로만 알던 규칙이 Skill 안에서 자동 적용됐다
- Review 지적 네 개 중 둘만 반영하고 둘은 부채로 넘겼다
- 사람이 결정한 지점은 여섯 곳이었다
- 총 6시간 중 절반이 검토와 결정이었다